using System;
using System.Linq;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Services;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Editors
{
	/// <summary>
	/// Provides a System.Drawing.Design.UITypeEditor for visually picking a backdrop.
	/// </summary>
	public class BackdropEditor : UITypeEditor
	{
		private FrameXmlImageService imageService;
		private Backdrop savedBackdrop;

		/// <summary>
		/// Gets the editor style used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"/> method.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that can be used to gain additional context information.</param>
		/// <returns>
		/// A <see cref="T:System.Drawing.Design.UITypeEditorEditStyle"/> value that indicates the style of editor used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"/> method. If the <see cref="T:System.Drawing.Design.UITypeEditor"/> does not support this method, then <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"/> will return <see cref="F:System.Drawing.Design.UITypeEditorEditStyle.None"/>.
		/// </returns>
		public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
		{
			return UITypeEditorEditStyle.Modal;
		}

		/// <summary>
		/// Edits the specified object's value using the editor style indicated by the <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"/> method.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that can be used to gain additional context information.</param>
		/// <param name="provider">An <see cref="T:System.IServiceProvider"/> that this editor can use to obtain services.</param>
		/// <param name="value">The object to edit.</param>
		/// <returns>
		/// The new value of the object. If the value of the object has not changed, this should return the same object it was passed.
		/// </returns>
		public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
		{
			if (provider != null)
			{
				if (context.PropertyDescriptor.Attributes != null)
				{
					//If Backdrop is read-only then return
					ReadOnlyAttribute attribute = context.PropertyDescriptor.Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault();
					if (attribute != null)
					{
						if (attribute.IsReadOnly) return value;
					}
				}

				var editorService = (IWindowsFormsEditorService) provider.GetService(typeof (IWindowsFormsEditorService));
				
				if (imageService == null)
					imageService = (FrameXmlImageService)provider.GetService(typeof(FrameXmlImageService));

				if (value == null) value = new Backdrop();

				//Save the original backdrop object for cancel operation
				SaveBackdropState((Backdrop)value);

				//Create the BackdropEditorForm instance and passed the current backdrop as parameter
				using (var editorForm = new BackdropEditorForm((Backdrop)value, provider))
				{
					if (editorService.ShowDialog(editorForm) == DialogResult.OK)
					{
						return editorForm.BackdropObject;
					}
					//Cancel button clicked therefore restore the Backdrop state
					return RestoreBackdropState();
				}
			}

			return base.EditValue(context, provider, value);
		}

		/// <summary>
		/// Indicates whether the specified context supports painting a representation of an object's value within the specified context.
		/// </summary>
		/// <returns>
		/// true if <see cref="M:System.Drawing.Design.UITypeEditor.PaintValue(System.Object,System.Drawing.Graphics,System.Drawing.Rectangle)"></see> is implemented; otherwise, false.
		/// </returns>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that can be used to gain additional context information. </param>
		public override bool GetPaintValueSupported(ITypeDescriptorContext context)
		{
			return true;
		}

		/// <summary>
		/// Paints a representation of the value of an object using the specified <see cref="T:System.Drawing.Design.PaintValueEventArgs"></see>.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Drawing.Design.PaintValueEventArgs"></see> that indicates what to paint and where to paint it. </param>
		public override void PaintValue(PaintValueEventArgs e)
		{
			base.PaintValue(e);

			if (e.Value != null)
			{
				try
				{
					var backdrop = (Backdrop)e.Value;

					if (imageService == null)
						imageService = (FrameXmlImageService)e.Context.GetService(typeof(FrameXmlImageService));

					//NOTE: draw edge of Backdrop if possible
					Image image = GetBackgroundImage(backdrop);

					if (image != null)
					{
						e.Graphics.DrawImage(ResizeImage(image, e.Bounds.Width, e.Bounds.Height), e.Bounds);
					}
				}
				catch (Exception ex)
				{
					Trace.WriteLine(ex);
				}
			}
		}

		/// <summary>
		/// Saves the state of the backdrop.
		/// </summary>
		/// <param name="backdrop">The backdrop.</param>
		private void SaveBackdropState(Backdrop backdrop)
		{
			try
			{
				var formatter = new BinaryFormatter();
				using(var stream = new MemoryStream())
				{
					formatter.Serialize(stream, backdrop);
					stream.Position = 0;
					savedBackdrop = formatter.Deserialize(stream) as Backdrop;
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		/// <summary>
		/// Restores the state of the backdrop.
		/// </summary>
		/// <returns></returns>
		private Backdrop RestoreBackdropState()
		{
			return savedBackdrop;
		}

		/// <summary>
		/// Gets the back ground image.
		/// </summary>
		/// <param name="backdrop">The backdrop.</param>
		/// <returns></returns>
		internal Image GetBackgroundImage(Backdrop backdrop)
		{
			if (backdrop != null)
			{
				if (backdrop.BackgroundFile.IsAvailable())
				{
					return imageService.GetImage(backdrop.BackgroundFile);
				}
			}
			return null;
		}

		/// <summary>
		/// Resizes the image.
		/// </summary>
		/// <param name="image">The image.</param>
		/// <param name="nWidth">Width of the n.</param>
		/// <param name="nHeight">Height of the n.</param>
		/// <returns></returns>
		internal static Image ResizeImage(Image image, int nWidth, int nHeight)
		{
			Image result = new Bitmap(nWidth, nHeight);

			using (Graphics g = Graphics.FromImage(result))
				g.DrawImage(image, 0, 0, nWidth, nHeight);

			return result;
		}
	}
}